package caretaker

import (
	"encoding/binary"
	"fmt"
	"gitee.com/dongmingchao/decent-ft/src/event"
	resourcePool "gitee.com/dongmingchao/decent-ft/src/resource-pool"
	"gitee.com/dongmingchao/decent-ft/src/utils"
	"github.com/fsnotify/fsnotify"
	"io/ioutil"
	"log"
	"os"
	"os/signal"
	"syscall"
)

func watchDir(dirname string, handler func(event fsnotify.Event)) *fsnotify.Watcher {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		log.Fatal(err)
	}

	go func() {
		for {
			select {
			case event, ok := <-watcher.Events:
				if !ok {
					return
				}
				log.Println("event:", event)
				handler(event)
				if event.Op&fsnotify.Write == fsnotify.Write {
					log.Println("modified file:", event.Name)
				}
			case err, ok := <-watcher.Errors:
				if !ok {
					return
				}
				log.Println("error:", err)
			}
		}
	}()

	err = watcher.Add(dirname) // "./src/resource-pool/sample-pool"
	if err != nil {
		log.Fatal(err)
	}
	return watcher
}

func (watcher *careWatcher) watchHandler(event fsnotify.Event) {
	length := len(watcher.fileNames)
	id := includeString(watcher.fileNames, event.Name)
	print("search file", id, length)
	fmt.Println(watcher.fileNames)
	if event.Op&fsnotify.Create == fsnotify.Create {
		if id == length {
			watcher.stashAppend(event.Name)
		}
	}
	if event.Op&fsnotify.Write == fsnotify.Write {
		if id == length {
			watcher.stashAppend(event.Name)
		} else {
			gfile := GlobalStash.Files[id]
			oldMarkStr := fmt.Sprintf("%x", gfile.Checksum)
			hashDir := StashDir + "/" + oldMarkStr[0:2]
			stashPath := hashDir + "/" + oldMarkStr[2:38]
			os.Remove(stashPath)
			println("remove", stashPath)
			_ = os.Remove(hashDir)
			utils.ReadFile(event.Name, func(file *os.File) {
				obj := stashFile(file)
				gfile.Checksum = obj.Mark
				gfile.FileSize = obj.GLen
			})
		}
	}
	if event.Op&fsnotify.Rename == fsnotify.Rename {
		println("rename", id, length)
		watcher.spliceStashFiles(id, length)
	}
	if event.Op&fsnotify.Remove == fsnotify.Remove {
		println("remove", id, length)
		watcher.spliceStashFiles(id, length)
	}
}

func includeString(watchNames []string, eventName string) int {
	id := 0
	for _, each := range watchNames {
		if each == eventName {
			break
		}
		id++
	}
	return id
}

func (watcher *careWatcher) spliceStashFiles(id int, length int) {
	if id == length {
		return
	}
	GlobalStash.Files = append(GlobalStash.Files[:id], GlobalStash.Files[id+1:]...)
	watcher.fileNames = append(watcher.fileNames[:id], watcher.fileNames[id+1:]...)
}

func (watcher *careWatcher) stashAppend(filename string) {
	println("add file", filename)
	var gfile resourcePool.GFile
	utils.ReadFile(filename, func(file *os.File) {
		fName := file.Name()
		obj := stashFile(file)
		gfile = resourcePool.GFile{
			FileSize: obj.GLen,
		}
		gfile.FileName = fName
		gfile.FileNameLen = uint16(len(fName))
		gfile.FileType = obj.GType
		gfile.Checksum = obj.Mark
	})
	GlobalStash.Files = append(GlobalStash.Files, &gfile)
	watcher.fileNames = append(watcher.fileNames, filename)
}

func stashFile(file *os.File) resourcePool.GHash {
	text, _ := ioutil.ReadAll(file)
	obj := resourcePool.NewGHash(text)
	os.MkdirAll(StashDir+"/"+obj.MarkStr[0:2], os.ModeDir|0700)
	f, err := os.OpenFile(getLocateByHash(obj.MarkStr), os.O_CREATE|os.O_RDWR, 0644)
	binary.Write(f, binary.BigEndian, obj.FullBody.Bytes())
	if err != nil {
		log.Println(err)
	}
	println("Stash File: ", getLocateByHash(obj.MarkStr))
	return obj
}

func removeStash() {

}

const (
	StashDir       = "./objects"
	StashIndexFile = StashDir + "/index"
)

func ReadIndex() resourcePool.GTree {
	stash := resourcePool.GTree{}
	if _, err := os.Stat(StashDir); os.IsNotExist(err) {
		os.Mkdir(StashDir, os.ModeDir|0700)
		fmt.Println("Create Stash Dir: ", StashDir)
	}
	if _, err := os.Stat(StashIndexFile); os.IsNotExist(err) {
		os.Create(StashIndexFile)
		fmt.Println("Create Stash Index file: ", StashIndexFile)
		stash.Version = 1
	} else {
		stashIndex, _ := os.Open(StashIndexFile)
		stash.Read(stashIndex)
		stashIndex.Close()
		fmt.Println("Found Stash Index file")
	}
	return stash
}

func SaveIndex(stash resourcePool.GTree) {
	stash.UpdateFileCount()
	stash.UpdateChecksum()
	fmt.Println("Finally Stash")
	fmt.Println(stash)

	stashIndex, _ := os.OpenFile(StashIndexFile, os.O_CREATE|os.O_RDWR, 0644)
	stash.Write(stashIndex)
	stashIndex.Close()
}

var GlobalStash resourcePool.GTree

type careWatcher struct {
	fileNames []string
}

func newCareWatcher(stash resourcePool.GTree) *careWatcher {
	var watcher careWatcher
	for _, file := range stash.Files {
		watcher.fileNames = append(watcher.fileNames, file.FileName)
	}
	return &watcher
}

func WatchDir(wg event.Bus, focusDir string) {
	sigs := make(chan os.Signal, 1)
	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
	var fileWatcher *fsnotify.Watcher
	go func() {
		GlobalStash = ReadIndex()
		watcher := newCareWatcher(GlobalStash)
		fileWatcher = watchDir(focusDir, watcher.watchHandler)
	}()
	wg.Emit("caretaker-ready")
	<-sigs
	err := fileWatcher.Close()
	if err != nil {
		log.Fatal(err)
	}
	SaveIndex(GlobalStash)
	defer wg.Emit("stop-caretaker")
}
